// 
// Author: damphat@gmail.com
//

#define _CRT_SECURE_NO_DEPRECATE
#define	_CRT_NON_CONFORMING_SWPRINTFS
#include "vn/DateTime.h"
#include <assert.h>

#include <stdio.h>						// review: _stprintf
#include <tchar.h>						// review: TCHAR

#ifdef _WIN32
#include <windows.h>					// for GetSystemTime()
#endif

#define MillisPerSecond		1000LL
#define MillisPerMinute		60000LL
#define MillisPerHour		3600000LL
#define MillisPerDay		86400000LL
#define DaysPerYear			365
#define DaysPer4Years		1461
#define DaysPer100Years		36524
#define DaysPer400Years		146097
#define DatePartYear		0
#define DatePartDayOfYear	1
#define DatePartMonth		2
#define DatePartDay			3


DateTime::DateTime( int year, int month, int day, int hour, int minute, int second, int millisecond ) {
	data =  DateToMilisecond(year, month, day) + TimeToMilisecond(hour, minute, second);
	data += millisecond;
}

DateTime::DateTime( int year, int month, int day, int hour, int minute, int second ) {
	data =  DateToMilisecond(year, month, day) + TimeToMilisecond(hour, minute, second);
}

DateTime::DateTime( int year, int month, int day ) {
	data = DateToMilisecond(year, month, day);
}

DateTime::DateTime( Int64 miliseconds /*= 0*/ )	{
	data = miliseconds;
}

const DateTime DateTime::GetDate() const {
	return DateTime(data - (data % MillisPerDay));
}

int DateTime::GetHour() const {
	return int(data / MillisPerHour % 24);
}

int DateTime::GetMinute() const	{
	return int(data / MillisPerMinute % 60);
}

int DateTime::GetSecond() const {
	return int(data / MillisPerSecond % 60);
}

int DateTime::GetMillisecond() const {
	return int(data % MillisPerSecond);
}

int DateTime::GetYear() const {
	return GetDatePart(DatePartYear);
}

int DateTime::GetMonth() const {
	return GetDatePart(DatePartMonth);
}

int DateTime::GetDay() const {
	return GetDatePart(DatePartDay);
}

int DateTime::GetDayOfWeek() const {
	return int((data / MillisPerDay + 1) % 7);
}

int DateTime::GetDayOfYear() const {
	return GetDatePart(DatePartDayOfYear);
}

const DateTime DateTime::Now() {
#ifdef _WIN32
	SYSTEMTIME st;
	GetSystemTime(&st);
	return DateTime(st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
#else
#error This is not Win32 OS! You should implement how to get system time on your OS.
#endif
}

const DateTime DateTime::Today() {
	return Now().GetDate();
}

bool DateTime::IsLeapYear( int year ) {
	assert((year >= 1) && (year <= 9999));

	if ((year % 4) != 0)
		return false;

	if ((year % 100) == 0)
		return ((year % 400) == 0);

	return true;
}

int DateTime::DaysInMonth( int year, int month ) {
	assert((month >= 1) && (month <= 12));

	int *numArray = IsLeapYear(year) ? DaysToMonth366 : DaysToMonth365;
	return (numArray[month] - numArray[month - 1]);
}

int DateTime::GetDatePart( int part ) const {
	int days = int(data / MillisPerDay);
	int n400 = days / DaysPer400Years;
	days -= n400 * DaysPer400Years;
	int n100 = days / DaysPer100Years;
	if (n100 == 4) {
		n100 = 3;
	}

	days -= n100 * DaysPer100Years;
	int n4 = days / DaysPer4Years;
	days -= n4 * DaysPer4Years;
	int n1 = days / DaysPerYear;
	if (n1 == 4) {
		n1 = 3;
	}

	if (part == DatePartYear) {
		return (((((n400 * 400) + (n100 * 100)) + (n4 * 4)) + n1) + 1);
	}

	days -= n1 * DaysPerYear;
	if (part == DatePartDayOfYear) {
		return (days + 1);
	}

	int *numArray = ((n1 == 3) && ((n4 != 0x18) || (n100 == 3))) ? DaysToMonth366 : DaysToMonth365;
	int index = days >> 6;
	while (days >= numArray[index]) {
		index++;
	}

	if (part == DatePartMonth) {
		return index;
	}

	return ((days - numArray[index - 1]) + 1);
}

Int64 DateTime::TimeToMilisecond( int hour, int minute, int second ) {
	return 1000LL * (((hour * 3600LL) + (minute * 60LL)) + second);
}

Int64 DateTime::DateToMilisecond( int year, int month, int day ) {
	assert(((year >= 1) && (year <= 9999)) && ((month >= 1) && (month <= 12)));

	int *numArray = IsLeapYear(year) ? DaysToMonth366 : DaysToMonth365;

	if ((day >= 1) && (day <= (numArray[month] - numArray[month - 1])))	{
		int num = year - 1;
		int num2 = ((((((num * DaysPerYear) + (num / 4)) - (num / 100)) + (num / 400)) + numArray[month - 1]) + day) - 1;
		return (num2 * MillisPerDay);
	}
	else {
		assert(false);
		return 0;					// unreached code expected!
	}
}

String DateTime::ToString() const
{
	TCHAR temp[200];
	_stprintf(temp, _T("%.2d/%.2d/%.4d %.2d:%.2d:%.2d.%.3d"),
		GetDay(),
		GetMonth(),
		GetYear(),
		GetHour(),
		GetMinute(),
		GetSecond(),
		GetMillisecond());

	return temp;
}

String DateTime::ToString( String format ) const
{
	TCHAR temp[200];
	if(format == _T("dd/mm/yyyy")) {
		_stprintf(temp, _T("%.2d/%.2d/%.4d"),
			GetDay(),
			GetMonth(),
			GetYear());
	}
	else if(format == _T("ddmmyyyy")) {
		_stprintf(temp, _T("%.2d%.2d%.4d"),
			GetDay(),
			GetMonth(),
			GetYear());
	}
	else if(format == _T("ddmmyy")) {
		_stprintf(temp, _T("%.2d%.2d%.2d"),
			GetDay(),
			GetMonth(),
			GetYear()% 100);
	}
	else if(format == _T("hh:mm:ss")) {
		_stprintf(temp, _T("%.2d:%.2d:%.2d"),
			GetHour(),
			GetMinute(),
			GetSecond());
	}
	else if(format == _T("hh:mm:ss.sss")) {
		_stprintf(temp, _T("%.2d:%.2d:%.2d.%.3d"),
			GetHour(),
			GetMinute(),
			GetSecond(),
			GetMillisecond());
	}

	return temp;
}

int DateTime::DaysToMonth365[] = { 0, 0x1f, 0x3b, 90, 120, 0x97, 0xb5, 0xd4, 0xf3, 0x111, 0x130, 0x14e, 0x16d };
int DateTime::DaysToMonth366[] = { 0, 0x1f, 60, 0x5b, 0x79, 0x98, 0xb6, 0xd5, 0xf4, 0x112, 0x131, 0x14f, 0x16e };

// operators
DateTime operator + (DateTime d, TimeSpan t) {
	return d.data + t.data;
}

DateTime operator - (DateTime d, TimeSpan t) {
	return d.data - t.data;
}

TimeSpan operator - (DateTime d1, DateTime d2) {
	return d1.data - d2.data;
}

bool operator ==(DateTime d1, DateTime d2) {
	return d1.data == d2.data;
}

bool operator !=(DateTime d1, DateTime d2) {
	return d1.data != d2.data;
}

bool operator < (DateTime t1, DateTime t2) {
	return t1.data < t2.data;
}

bool operator <=(DateTime t1, DateTime t2) {
	return t1.data <= t2.data;
}

bool operator > (DateTime t1, DateTime t2) {
	return t1.data > t2.data;
}

bool operator >=(DateTime t1, DateTime t2) {
	return t1.data >= t2.data;
}
